﻿/*
 * File: TileSetDatabase.cs
 * Developer : Brent Larsen (brentoboy@gmail.com)
 * Date: 2011-AUG-13
 * Copyright (c) 2011 Brent Larsen.
 * Distributed under the terms of the GPL v3
 *  
 * This file is part of GenX-RPG
 * http://code.google.com/p/genx-rpg/ 
 * 
 * GenX-RPG is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GenX-RPG is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GenX-RPG.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Data;
using System.Windows.Forms;

namespace Rpg
{
    public class TileSetDatabase : IDisposable
    {
        public static string GenxRpgDataFolder 
        { 
            get { return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\GenX-RPG"; } 
        }

        public string DbFileName { get; private set; }

        private const string ConnectionStringTemplate = "Data Source={0};Version=3;";
        private SQLiteConnection _db;

        public static void __createAppDataFolder()
        {
            var folder = GenxRpgDataFolder;
            if (!Directory.Exists(folder))
                Directory.CreateDirectory(folder);
        }

        public static Dictionary<string, string> GetTileSetList()
        {
            var dict = new Dictionary<string, string>();

            __createAppDataFolder();
            var tileDatabases = Directory.GetFiles(GenxRpgDataFolder, "*.tiles");

            foreach (var tileDb in tileDatabases)
            {
                var filename = new FileInfo(tileDb).Name;
                using (var db = new TileSetDatabase(filename))
                {
                    dict.Add(filename, db.GetTileSetName());
                }
            }

            return dict;
        }

        public TileSetDatabase(string dbFileName)
        {
            this.DbFileName = dbFileName;

            var dbPath = GenxRpgDataFolder + "\\" + dbFileName;
            Contract.Requires(File.Exists(dbPath));

            _db = new SQLiteConnection(ConnectionStringTemplate.f(dbPath));
            _db.Open();
        }

        public void SaveAs(FileInfo path)
        {
            if (path.Extension.ToLower() != ".tiles")
                path = new FileInfo(path.FullName + ".tiles");

            File.Copy(GenxRpgDataFolder + "\\" + this.DbFileName, path.FullName);
        }

        public static Nullable<KeyValuePair<string, string>> Import(FileInfo path)
        {
            // stick the new file in the app data folder so we can connect to it
            Contract.Requires(path.Exists);
            var tempPath = GenxRpgDataFolder + "\\" + path.Name;
            File.Copy(path.FullName, tempPath);
            
            //ask the temprary database about itself
            string uniqueId;
            string name;
            using (var tempDb = new TileSetDatabase(path.Name))
            {
                uniqueId = tempDb.GetUniqueId();
                name = tempDb.GetTileSetName();
            }

            //rename the file to be what the unique ID wants it to be
            string correctDbName = uniqueId + ".tiles";
            string correctPath = GenxRpgDataFolder + "\\" + correctDbName;
            if (path.Name != correctDbName)
            {
                if (File.Exists(correctPath))
                {
                    if (DialogResult.Cancel != MessageBox.Show(null, "This tileset is already in your game master's toolkit, do you wish to overwrite the existing copy?", "Overwrite?", MessageBoxButtons.OKCancel))
                    {
                        File.Delete(correctPath);
                    }
                    else
                    {
                        File.Delete(tempPath);
                        return null;
                    }
                }
                File.Move(tempPath, correctPath);
            }

            // report back what the new tileset is (so we can add it to the menu or whatever else)
            return new KeyValuePair<string,string>(correctDbName, name);
        }

        public TileSetDatabase(FileInfo bmpFile)
        {
            Contract.Requires(bmpFile.Exists);
            var bmpData = File.ReadAllBytes(bmpFile.FullName);

            string uniqueID = Extenders.GetUniqueStringID();
            this.DbFileName = uniqueID + ".tiles";
            var dbPath = GenxRpgDataFolder + "\\" + this.DbFileName;
            Contract.Asserts(!File.Exists(dbPath));

            _db = new SQLiteConnection(ConnectionStringTemplate.f(dbPath));
            _db.Open();

            using (SQLiteTransaction transaction = _db.BeginTransaction())
            {
                _db.Execute(Properties.Resources.CreateTileSet);


                using (var cmd = _db.CreateCommand())
                {
                    cmd.CommandText = "update tile_set set name = @name, unique_id = @unique_id, bitmap = @bitmap;";
                    cmd.Parameters.Add(new SQLiteParameter("@name", DbType.String));
                    cmd.Parameters.Add(new SQLiteParameter("@unique_id", DbType.String));
                    cmd.Parameters.Add(new SQLiteParameter("@bitmap", DbType.Binary));
                    cmd.Parameters["@name"].Value = bmpFile.Name.Replace(bmpFile.Extension, "");
                    cmd.Parameters["@unique_id"].Value = uniqueID;
                    cmd.Parameters["@bitmap"].Value = bmpData;

                    var rowsAffected = cmd.ExecuteNonQuery();
                    Contract.Asserts(rowsAffected == 1);
                }

                transaction.Commit();
            }
        }

        public string GetTileSetName()
        {
            return _db.Get("select name from tile_set;").ToString();
        }

        public string GetUniqueId()
        {
            return _db.Get("select unique_id from tile_set;").ToString();
        }

        public TileSet LoadTileSet()
        {
            TileSet tileSet = null;

            using (var reader = _db.Query(
                "select unique_id, name, copyright_owner, date_originally_published, copyright_notice, license, bitmap " +
                "from tile_set;"))
            {
                Contract.Asserts(reader.Read());

                var size = (int)(long)reader.GetBytes(6, 0, null, 0, 0);

                tileSet = new TileSet()
                {
                    UniqueId = reader.GetString(0),
                    Name = reader.GetString(1),
                    CopyrightOwner = reader.GetString(2),
                    DateOriginallyPublished = reader.GetString(3),
                    CopyrightNotice = reader.GetString(4),
                    License = reader.GetString(5),
                    BitmapData = new byte[size],
                };

                reader.GetBytes(6, 0, tileSet.BitmapData, 0, size);

                Contract.Asserts(!reader.Read()); // there should not be more than one row
            }

            using (var reader = _db.Query("select 0 as id, unique_id, name, x, y, group_name, group_x, group_y, walk from tile;"))
            {
                while (reader.Read())
                {
                    var tile = new Tile(
                        id: reader.GetInt32(0),
                        uniqueId: reader.GetString(1),
                        name: reader.GetString(2),
                        bmpX: reader.GetInt32(3),
                        bmpY: reader.GetInt32(4),
                        groupName: reader.GetString(5),
                        groupX: reader.GetInt32(6),
                        groupY: reader.GetInt32(7),
                        walk: reader.GetInt32(8)
                        );

                    tileSet.Tiles.Add(tile);
                }
            }

            return tileSet;
        }

        public void SaveTileSet(TileSet tileSet)
        {
            Contract.Assumes(tileSet.Width.HasValue);
            Contract.Assumes(tileSet.Height.HasValue);

            using (SQLiteTransaction transaction = _db.BeginTransaction())
            {
                var rowsAffected = _db.Execute(
                    "update tile_set set name = {0}" +
                    ", copyright_owner = {1}, date_originally_published = {2}, copyright_notice = {3}, license = {4};"
                    , _db.EncodeText(tileSet.Name)
                    , _db.EncodeText(tileSet.CopyrightOwner)
                    , _db.EncodeText(tileSet.DateOriginallyPublished)
                    , _db.EncodeText(tileSet.CopyrightNotice)
                    , _db.EncodeText(tileSet.License)
                    );
                Contract.Asserts(rowsAffected == 1);

                foreach (var tile in tileSet.Tiles)
                {
                    if (tile.BmpX > tileSet.Width * Tile.SQUARE_SIZE
                        || tile.BmpY > tileSet.Height * Tile.SQUARE_SIZE)
                    {
                        // delete it, it no longer fits on the current bitmap
                        if (!tile.UniqueId.IsBlank())
                        {
                            var rowAffected = _db.Execute(
                                "delete from tile where unique_id = {0};"
                                , _db.EncodeText(tile.UniqueId)
                                );
                            Contract.Asserts(rowsAffected == 1);
                        }
                    }
                    else
                    {
                        if (tile.UniqueId.IsBlank())
                        {
                            tile.UniqueId = Extenders.GetUniqueStringID();

                            rowsAffected = _db.Execute(
                                "insert into tile (unique_id, name, x, y, group_name, group_x, group_y, walk) " +
                                "values ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7});"
                                , _db.EncodeText(tile.UniqueId)
                                , _db.EncodeText(tile.Name)
                                , tile.BmpX / Tile.SQUARE_SIZE
                                , tile.BmpY / Tile.SQUARE_SIZE
                                , _db.EncodeText(tile.GroupName)
                                , tile.GroupX
                                , tile.GroupY
                                , tile.Walk
                                );
                            Contract.Asserts(rowsAffected == 1);
                        }
                        else
                        {
                            rowsAffected = _db.Execute(
                                "update tile " +
                                "set name = {1}" +
                                ", x = {2}" +
                                ", y = {3}" +
                                ", group_name = {4}" +
                                ", group_x = {5}" +
                                ", group_y = {6}" +
                                ", walk = {7} " +
                                "where unique_id = {0}"
                                , _db.EncodeText(tile.UniqueId)
                                , _db.EncodeText(tile.Name)
                                , tile.BmpX / Tile.SQUARE_SIZE
                                , tile.BmpY / Tile.SQUARE_SIZE
                                , _db.EncodeText(tile.GroupName)
                                , tile.GroupX
                                , tile.GroupY
                                , tile.Walk
                                );
                            Contract.Asserts(rowsAffected == 1);
                        }
                    }
                }

                transaction.Commit();
            }
        }

        public void Dispose()
        {
            _db.Close();
            _db = null;
        }
    }
}
